{
 "metadata": {
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4-final"
  },
  "orig_nbformat": 2,
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3",
   "language": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2,
 "cells": [
  {
   "source": [
    "# 廖雪峰 Python 教程"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "## 3.高级特性"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "### 3.4.生成器"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "杨辉三角定义如下\n",
    "\n",
    "``` shell\n",
    "            1\n",
    "           / \\\n",
    "          1   1\n",
    "         / \\ / \\\n",
    "        1   2   1\n",
    "       / \\ / \\ / \\\n",
    "      1   3   3   1\n",
    "     / \\ / \\ / \\ / \\\n",
    "    1   4   6   4   1\n",
    "   / \\ / \\ / \\ / \\ / \\\n",
    "  1   5   10  10  5   1\n",
    "```"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "def triangles():\n",
    "    n = 0\n",
    "    a = [1]\n",
    "    b = [1, 1]\n",
    "\n",
    "    while n >= 0:\n",
    "        if n == 0:\n",
    "            yield a\n",
    "            n += 1\n",
    "        elif n == 1:\n",
    "            yield b\n",
    "            n += 1\n",
    "        else:\n",
    "            c = [1] * (n + 1)\n",
    "            for i in range(1, n):\n",
    "                c[i] = b[i] + b[i - 1]  # 当前行与上一行的关系\n",
    "            yield c\n",
    "            b = c  #  当前行变成下一轮的上一行\n",
    "            n += 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "[1]\n[1, 1]\n[1, 2, 1]\n[1, 3, 3, 1]\n[1, 4, 6, 4, 1]\n[1, 5, 10, 10, 5, 1]\n[1, 6, 15, 20, 15, 6, 1]\n[1, 7, 21, 35, 35, 21, 7, 1]\n[1, 8, 28, 56, 70, 56, 28, 8, 1]\n[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]\n"
     ]
    }
   ],
   "source": [
    "n = 0\n",
    "results = []\n",
    "for t in triangles():\n",
    "    results.append(t)\n",
    "    n = n + 1\n",
    "    if n == 10:\n",
    "        break\n",
    "\n",
    "for t in results:\n",
    "    print(t)"
   ]
  },
  {
   "source": [
    "## 4.函数式编程"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "### 4.1.高阶函数"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "#### 4.1.1. map/reduce"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "1. 利用 `map()` 函数，把用户输入的不规范的英文名字，变为首字母大写，其他小写的规范名字。输入：`['adam', 'LISA', 'barT']`，输出：`['Adam', 'Lisa', 'Bart']`："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "def normalize(name):\n",
    "    name = name.lower()\n",
    "    name = name[0].upper() + name[1:]\n",
    "    \n",
    "    return name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "['Adam', 'Lisa', 'Bart']\n"
     ]
    }
   ],
   "source": [
    "\n",
    "\n",
    "# 测试:\n",
    "L1 = ['adam', 'LISA', 'barT']\n",
    "L2 = list(map(normalize, L1))\n",
    "print(L2)"
   ]
  },
  {
   "source": [
    "2. Python 提供的 `sum()` 函数可以接受一个 list 并求和，请编写一个 `prod()` 函数，可以接受一个 list 并利用 `reduce()` 求积："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "from functools import reduce\n",
    "\n",
    "def prod(L):\n",
    "    fn = lambda x, y: x * y\n",
    "    return reduce(fn, L)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "3 * 5 * 7 * 9 = 945\n测试成功!\n"
     ]
    }
   ],
   "source": [
    "print('3 * 5 * 7 * 9 =', prod([3, 5, 7, 9]))\n",
    "if prod([3, 5, 7, 9]) == 945:\n",
    "    print('测试成功!')\n",
    "else:\n",
    "    print('测试失败!')"
   ]
  },
  {
   "source": [
    "3. 利用 `map` 和 `reduce` 编写一个 `str2float` 函数，把字符串 `'123.456'` 转换成浮点数 `123.456` ："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [],
   "source": [
    "from functools import reduce\n",
    "\n",
    "\n",
    "def str2float(s):\n",
    "    s = s.split('.')\n",
    "    s_int = s[0]  # 整数部分\n",
    "    s_float = s[-1]  # 小数部分\n",
    "    s_float = s_float[-1::-1]  # 小数部分翻转\n",
    "\n",
    "    f_int = lambda x, y: 10 * x + y  # 整数计算规则\n",
    "    f_float = lambda x, y: 0.1 * x + y  # 小数部分计算规则\n",
    "    char2num = lambda s: DIGITS[s]\n",
    "\n",
    "    m = reduce(f_int, map(char2num, s_int ))\n",
    "    n = reduce(f_float, map(char2num, s_float))\n",
    "\n",
    "    return m + 0.1 * n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "str2float('123.456') = 123.456\n测试成功!\n"
     ]
    }
   ],
   "source": [
    "DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, \n",
    "          '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}\n",
    "\n",
    "print('str2float(\\'123.456\\') =', str2float('123.456'))\n",
    "if abs(str2float('123.456') - 123.456) < 0.00001:\n",
    "    print('测试成功!')\n",
    "else:\n",
    "    print('测试失败!')"
   ]
  },
  {
   "source": [
    "#### 4.1.2.filter"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "回数是指从左向右读和从右向左读都是一样的数，例如 `12321`，`909`。请利用`filter()` 筛选出回数："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "def is_palindrome(n):\n",
    "    s = str(n)\n",
    "    index_middle = int(len(s) / 2)\n",
    "    flag = True\n",
    "    \n",
    "    for i in range(index_middle):\n",
    "        if s[i] != s[-1 - i]:\n",
    "            flag = False\n",
    "            break\n",
    "    \n",
    "    return flag"
   ],
   "cell_type": "code",
   "metadata": {},
   "execution_count": 51,
   "outputs": []
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "1~1000: [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191, 202, 212, 222, 232, 242, 252, 262, 272, 282, 292, 303, 313, 323, 333, 343, 353, 363, 373, 383, 393, 404, 414, 424, 434, 444, 454, 464, 474, 484, 494, 505, 515, 525, 535, 545, 555, 565, 575, 585, 595, 606, 616, 626, 636, 646, 656, 666, 676, 686, 696, 707, 717, 727, 737, 747, 757, 767, 777, 787, 797, 808, 818, 828, 838, 848, 858, 868, 878, 888, 898, 909, 919, 929, 939, 949, 959, 969, 979, 989, 999]\n测试成功!\n"
     ]
    }
   ],
   "source": [
    "# 测试:\n",
    "output = filter(is_palindrome, range(1, 1000))\n",
    "print('1~1000:', list(output))\n",
    "if list(filter(is_palindrome, range(1, 200))) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191]:\n",
    "    print('测试成功!')\n",
    "else:\n",
    "    print('测试失败!')"
   ]
  },
  {
   "source": [
    "#### 4.1.3.sorted"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "`L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]`\n",
    "\n",
    "用 `sorted()` 对上述列表分别按照名字排序"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "[('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]\n"
     ]
    }
   ],
   "source": [
    "def by_name(t):\n",
    "    return str.lower(t[0])\n",
    "\n",
    "\n",
    "L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]\n",
    "L2 = sorted(L, key=by_name)\n",
    "print(L2)"
   ]
  },
  {
   "source": [
    "再按照分数降序排序"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "[('Adam', 92), ('Lisa', 88), ('Bob', 75), ('Bart', 66)]\n"
     ]
    }
   ],
   "source": [
    "def by_score(t):\n",
    "    return - t[-1]\n",
    "\n",
    "\n",
    "L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]\n",
    "L2 = sorted(L, key=by_score)\n",
    "print(L2)"
   ]
  },
  {
   "source": [
    "### 4.2.返回函数"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "利用闭包返回一个计数器函数，每次调用它返回递增整数："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "def createCounter():\n",
    "\n",
    "    def iterator():  # 定义一个生成器\n",
    "        c = 0\n",
    "        while True:\n",
    "            c += 1\n",
    "            yield c\n",
    "\n",
    "    g = iterator()\n",
    "\n",
    "    def counter():\n",
    "        return next(g)\n",
    "    return counter"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "1 2 3 4 5\n测试通过!\n"
     ]
    }
   ],
   "source": [
    "# 测试:\n",
    "counterA = createCounter()\n",
    "print(counterA(), counterA(), counterA(), counterA(), counterA())  # 1 2 3 4 5\n",
    "counterB = createCounter()\n",
    "if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:\n",
    "    print('测试通过!')\n",
    "else:\n",
    "    print('测试失败!')"
   ]
  },
  {
   "source": [
    "闭包的概念还需要再复习"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "### 4.3.匿名函数"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "请用匿名函数改造下面的代码："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]\n"
     ]
    }
   ],
   "source": [
    "def is_odd(n):ß\n",
    "    return n % 2 == 1\n",
    "\n",
    "\n",
    "L = list(filter(is_odd, range(1, 20)))\n",
    "print(L)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]\n"
     ]
    }
   ],
   "source": [
    "L = list(filter(lambda n: n % 2 == 1, range(1, 20)))\n",
    "print(L)"
   ]
  },
  {
   "source": [
    "### 4.4.装饰器"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "简单装饰器"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def use_logging(func):\n",
    "\n",
    "    def wrapper(*args, **kwargs):\n",
    "        print(\"%s is running.\" % func.__name__)\n",
    "        return func(*args)\n",
    "    return wrapper\n",
    "\n",
    "\n",
    "@use_logging\n",
    "def foo():\n",
    "    print(\"I am foo.\")\n",
    "\n",
    "\n",
    "@use_logging\n",
    "def bar():\n",
    "    print(\"I am bar.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "bar is running.\nI am bar.\n"
     ]
    }
   ],
   "source": [
    "bar()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "foo is running.\nI am foo.\n"
     ]
    }
   ],
   "source": [
    "foo()"
   ]
  },
  {
   "source": [
    "带参数的装饰器"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "foo is running.\nI am foo.\n"
     ]
    }
   ],
   "source": [
    "def use_logging(level):\n",
    "\n",
    "    def decorator(func):  # 是对原有装饰器的一个函数封装，并返回一个装饰器\n",
    "\n",
    "        def wrapper(*args, **kwargs):\n",
    "            if level == 'warn':\n",
    "                print(\"%s is running.\" % func.__name__)\n",
    "            return func(*args)\n",
    "\n",
    "        return wrapper  # 返回装饰器（装饰器是闭包结构）\n",
    "\n",
    "    return decorator\n",
    "\n",
    "\n",
    "@use_logging(level='warn')\n",
    "def foo(name='foo'):\n",
    "    print(\"I am %s.\" % name)\n",
    "\n",
    "\n",
    "foo()"
   ]
  },
  {
   "source": [
    "类装饰器"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "class decorator running.\nbar\nclass decorator ending.\n"
     ]
    }
   ],
   "source": [
    "class Foo(object):\n",
    "    \n",
    "    def __init__(self, func):\n",
    "        self._func = func\n",
    "    \n",
    "    def __call__(self):  # 内部 __call__ 方法\n",
    "        print('class decorator running.')\n",
    "        self._func()\n",
    "        print('class decorator ending.')\n",
    "\n",
    "\n",
    "@Foo\n",
    "def bar():\n",
    "    print('bar')\n",
    "\n",
    "\n",
    "bar()"
   ]
  },
  {
   "source": [
    "`functools.wraps`"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "with_logging\nNone\n"
     ]
    }
   ],
   "source": [
    "def logged(func):\n",
    "    def with_logging(*args, **kwargs):\n",
    "        print(func.__name__ + \" was called.\")\n",
    "        return func(*args, **kwargs)\n",
    "    return with_logging\n",
    "\n",
    "\n",
    "@logged\n",
    "def f(x):\n",
    "    \"\"\"does sme math\"\"\"\n",
    "    return x + x * x\n",
    "\n",
    "\n",
    "print(f.__name__)\n",
    "print(f.__doc__)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "f\ndoes some math\n"
     ]
    }
   ],
   "source": [
    "from functools import wraps\n",
    "\n",
    "def logged(func):\n",
    "    @wraps(func)\n",
    "    def with_logging(*args, **kwargs):\n",
    "        print(func.__name__ + \" was called.\")\n",
    "        return func(*args, **kwargs)\n",
    "    return with_logging\n",
    "\n",
    "\n",
    "@logged\n",
    "def f(x):\n",
    "    \"\"\"does some math\"\"\"\n",
    "    return x + x * x\n",
    "\n",
    "\n",
    "print(f.__name__)\n",
    "print(f.__doc__)"
   ]
  },
  {
   "source": [
    "内置装饰器"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "`@staticmethod` `@classmethod` `@property`\n",
    "\n",
    "装饰器的顺序\n",
    "\n",
    "```python\n",
    "@a\n",
    "@b\n",
    "@c\n",
    "def f():\n",
    "```\n",
    "\n",
    "等效于\n",
    "f = a(b(c(f)))"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "## 6.面向对象编程"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "### 6.2.访问限制"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "请把下面的 `Student` 对象的 `gender` 字段对外隐藏起来，用 `get_gender()` 和 `set_gender()` 代替，并检查参数有效性："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Student(object):\n",
    "\n",
    "    def __init__(self, name, gender):\n",
    "        self.__name = name\n",
    "        self.__gender = gender\n",
    "\n",
    "    def get_gender(self):\n",
    "        return self.__gender\n",
    "    \n",
    "    def set_gender(self, gender):\n",
    "        # 检查参数有效性\n",
    "        if gender == 'female' or gender == 'male': \n",
    "            self.__gender = gender\n",
    "        else:\n",
    "            print('输入有误')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "测试成功!\n"
     ]
    }
   ],
   "source": [
    "# 测试:\n",
    "bart = Student('Bart', 'male')\n",
    "if bart.get_gender() != 'male':\n",
    "    print('测试失败!')\n",
    "else:\n",
    "    bart.set_gender('female')\n",
    "    if bart.get_gender() != 'female':\n",
    "        print('测试失败!')\n",
    "    else:\n",
    "        print('测试成功!')"
   ]
  },
  {
   "source": [
    "### 6.5.实例属性和类属性"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "为了统计学生人数，可以给 Student 类增加一个类属性，每创建一个实例，该属性自动增加："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Student(object):\n",
    "    count = 0\n",
    "\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "        Student.count += 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "Students: 2\n测试通过!\n"
     ]
    }
   ],
   "source": [
    "# 测试:\n",
    "if Student.count != 0:\n",
    "    print('测试失败!')\n",
    "else:\n",
    "    bart = Student('Bart')\n",
    "    if Student.count != 1:\n",
    "        print('测试失败!')\n",
    "    else:\n",
    "        lisa = Student('Bart')\n",
    "        if Student.count != 2:\n",
    "            print('测试失败!')\n",
    "        else:\n",
    "            print('Students:', Student.count)\n",
    "            print('测试通过!')"
   ]
  },
  {
   "source": [
    "## 7.面向对象高级编程"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "### 7.2.使用 `@property`"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "请利用 `@property` 给一个 `Screen` 对象加上 `width` 和 `height` 属性，以及一个只读属性 `resolution`："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Screen(object):\n",
    "    \n",
    "    @property\n",
    "    def width(self):\n",
    "        return self._width\n",
    "    \n",
    "    @width.setter\n",
    "    def width(self, value):\n",
    "        self._width = value\n",
    "\n",
    "    @property\n",
    "    def height(self):\n",
    "        return self._height\n",
    "    \n",
    "    @height.setter\n",
    "    def height(self, value):\n",
    "        self._height = value\n",
    "    \n",
    "    @property\n",
    "    def resolution(self):\n",
    "        return self._width * self._height"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "resolution = 786432\n测试通过!\n"
     ]
    }
   ],
   "source": [
    "# 测试:\n",
    "s = Screen()\n",
    "s.width = 1024\n",
    "s.height = 768\n",
    "print('resolution =', s.resolution)\n",
    "if s.resolution == 786432:\n",
    "    print('测试通过!')\n",
    "else:\n",
    "    print('测试失败!')"
   ]
  },
  {
   "source": [
    "## 8.错误、调试和测试"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "### 8.1.错误处理"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "try:\n",
    "    print('try...')\n",
    "    r = 10 / 0\n",
    "    print('result:', r)\n",
    "except ZeroDivisionError as e:\n",
    "    print('except:', e)\n",
    "finally:\n",
    "    print('finally...')\n",
    "print('END')"
   ],
   "cell_type": "code",
   "metadata": {},
   "execution_count": 2,
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "try...\nexcept: division by zero\nfinally...\nEND\n"
     ]
    }
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "try...\nValueError: invalid literal for int() with base 10: 'a'\nfinally...\nEND\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    print('try...')\n",
    "    r = 10 / int('a')\n",
    "    print('result:', r)\n",
    "except ValueError as e:\n",
    "    print('ValueError:', e)\n",
    "except ZeroDivisionError as e:\n",
    "    print('ZeroDivisionError:', e)\n",
    "finally:\n",
    "    print('finally...')\n",
    "print('END')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "try...\nresult: 5.0\nno error!\nfinally...\nEND\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    print('try...')\n",
    "    r = 10 / int('2')\n",
    "    print('result:', r)\n",
    "except ValueError as e:\n",
    "    print('ValueError:', e)\n",
    "except ZeroDivisionError as e:\n",
    "    print('ZeroDivisionError:', e)\n",
    "else:\n",
    "    print('no error!')\n",
    "finally:\n",
    "    print('finally...')\n",
    "print('END')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def foo(s):\n",
    "    return 10 / int(s)\n",
    "\n",
    "def bar(s):\n",
    "    return foo(s) * 2\n",
    "\n",
    "def main():\n",
    "    try:\n",
    "        bar('0')\n",
    "    except Exception as e:\n",
    "        print('Error:', e)\n",
    "    finally:\n",
    "        print('finally...')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "Error: division by zero\nfinally...\n"
     ]
    }
   ],
   "source": [
    "main()"
   ]
  },
  {
   "source": [
    "运行下面的代码，根据异常信息进行分析，定位出错误源头，并修复："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "100 + 200 + 345 = 645\ninvalid literal for int() with base 10: ' 7.6'\n99 + 88 + 7.6 = 194.6\n"
     ]
    }
   ],
   "source": [
    "from functools import reduce\n",
    "\n",
    "\n",
    "def str2num(s):\n",
    "    try:\n",
    "        return int(s)\n",
    "    except ValueError as e:\n",
    "        print(e)\n",
    "        return float(s)\n",
    "\n",
    "\n",
    "def calc(exp):\n",
    "    ss = exp.split('+')\n",
    "    ns = map(str2num, ss)\n",
    "    return reduce(lambda acc, x: acc + x, ns)\n",
    "\n",
    "\n",
    "def main():\n",
    "    r = calc('100 + 200 + 345')\n",
    "    print('100 + 200 + 345 =', r)\n",
    "    r = calc('99 + 88 + 7.6')\n",
    "    print('99 + 88 + 7.6 =', r)\n",
    "\n",
    "\n",
    "main()"
   ]
  },
  {
   "source": [
    "用 `raise` 语句抛出一个错误的实例"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "output_type": "error",
     "ename": "FooError",
     "evalue": "invalid value: 0",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mFooError\u001b[0m                                  Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-1-231479a668cb>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m     10\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     11\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 12\u001b[1;33m \u001b[0mfoo\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'0'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;32m<ipython-input-1-231479a668cb>\u001b[0m in \u001b[0;36mfoo\u001b[1;34m(s)\u001b[0m\n\u001b[0;32m      6\u001b[0m     \u001b[0mn\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0ms\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      7\u001b[0m     \u001b[1;32mif\u001b[0m \u001b[0mn\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 8\u001b[1;33m         \u001b[1;32mraise\u001b[0m \u001b[0mFooError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'invalid value: %s'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0ms\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m      9\u001b[0m     \u001b[1;32mreturn\u001b[0m \u001b[1;36m10\u001b[0m \u001b[1;33m/\u001b[0m \u001b[0mn\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     10\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mFooError\u001b[0m: invalid value: 0"
     ]
    }
   ],
   "source": [
    "class FooError(ValueError):\n",
    "    pass\n",
    "\n",
    "\n",
    "def foo(s):\n",
    "    n = int(s)\n",
    "    if n == 0:\n",
    "        raise FooError('invalid value: %s' % s)\n",
    "    return 10 / n\n",
    "\n",
    "\n",
    "foo('0')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "ValueError!\n"
     ]
    },
    {
     "output_type": "error",
     "ename": "ValueError",
     "evalue": "invalid value: 0",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-3-4ab43a7b925f>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m     14\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     15\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 16\u001b[1;33m \u001b[0mbar\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;32m<ipython-input-3-4ab43a7b925f>\u001b[0m in \u001b[0;36mbar\u001b[1;34m()\u001b[0m\n\u001b[0;32m      8\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mbar\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      9\u001b[0m     \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 10\u001b[1;33m         \u001b[0mfoo\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'0'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     11\u001b[0m     \u001b[1;32mexcept\u001b[0m \u001b[0mValueError\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     12\u001b[0m         \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'ValueError!'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m<ipython-input-3-4ab43a7b925f>\u001b[0m in \u001b[0;36mfoo\u001b[1;34m(s)\u001b[0m\n\u001b[0;32m      2\u001b[0m     \u001b[0mn\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0ms\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      3\u001b[0m     \u001b[1;32mif\u001b[0m \u001b[0mn\u001b[0m\u001b[1;33m==\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m         \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'invalid value: %s'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0ms\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m      5\u001b[0m     \u001b[1;32mreturn\u001b[0m \u001b[1;36m10\u001b[0m \u001b[1;33m/\u001b[0m \u001b[0mn\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      6\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mValueError\u001b[0m: invalid value: 0"
     ]
    }
   ],
   "source": [
    "def foo(s):\n",
    "    n = int(s)\n",
    "    if n==0:\n",
    "        raise ValueError('invalid value: %s' % s)\n",
    "    return 10 / n\n",
    "\n",
    "\n",
    "def bar():\n",
    "    try:\n",
    "        foo('0')\n",
    "    except ValueError as e:\n",
    "        print('ValueError!')\n",
    "        raise\n",
    "\n",
    "\n",
    "bar()"
   ]
  },
  {
   "source": [
    "作业：运行下面的代码，根据异常信息进行分析，定位出错误源头，并修复：\n",
    "\n",
    "``` python\n",
    "from functools import reduce\n",
    "\n",
    "def str2num(s):\n",
    "    return int(s)\n",
    "\n",
    "def calc(exp):\n",
    "    ss = exp.split('+')\n",
    "    ns = map(str2num, ss)\n",
    "    return reduce(lambda acc, x: acc + x, ns)\n",
    "\n",
    "def main():\n",
    "    r = calc('100 + 200 + 345')\n",
    "    print('100 + 200 + 345 =', r)\n",
    "    r = calc('99 + 88 + 7.6')\n",
    "    print('99 + 88 + 7.6 =', r)\n",
    "\n",
    "main()\n",
    "```"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "### 8.3.单元测试"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 编写一个 Dict 类\n",
    "class Dict(dict):\n",
    "\n",
    "    def __init__(self, **kw):\n",
    "        super().__init__(**kw)\n",
    "\n",
    "    def __getattr__(self, key):\n",
    "        try:\n",
    "            return self[key]\n",
    "        except KeyError:\n",
    "            raise AttributeError(r\"'Dict' object has no attribute '%s'\" % key)\n",
    "\n",
    "    def __setattr__(self, key, value):\n",
    "        self[key] = value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "import unittest\n",
    "\n",
    "class TestDict(unittest.TestCase):\n",
    "\n",
    "    def test_init(self):\n",
    "        d = Dict(a=1, b='test')\n",
    "        self.assertEqual(d.a, 1)\n",
    "        self.assertEqual(d.b, 'test')\n",
    "        self.assertTrue(isinstance(d, dict))\n",
    "\n",
    "    def test_key(self):\n",
    "        d = Dict()\n",
    "        d['key'] = 'value'\n",
    "        self.assertEqual(d.key, 'value')\n",
    "\n",
    "    def test_attr(self):\n",
    "        d = Dict()\n",
    "        d.key = 'value'\n",
    "        self.assertTrue('key' in d)\n",
    "        self.assertEqual(d['key'], 'value')\n",
    "\n",
    "    def test_keyerror(self):\n",
    "        d = Dict()\n",
    "        with self.assertRaises(KeyError):\n",
    "            value = d['empty']\n",
    "\n",
    "    def test_attrerror(self):\n",
    "        d = Dict()\n",
    "        with self.assertRaises(AttributeError):\n",
    "            value = d.empty    "
   ]
  },
  {
   "source": [
    "对 Student 类编写单元测试，结果发现测试不通过，请修改 Student 类，让测试通过："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Student(object):\n",
    "\n",
    "    def __init__(self, name, score):\n",
    "        self.name = name\n",
    "        self.score = score\n",
    "\n",
    "    def get_grade(self):\n",
    "        if self.score < 0 or self.score > 100:\n",
    "            raise ValueError\n",
    "        if self.score >= 80:\n",
    "            return 'A'\n",
    "        if self.score >= 60:\n",
    "            return 'B'\n",
    "        return 'C'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stderr",
     "text": [
      "usage: ipykernel_launcher.py [-h] [-v] [-q] [--locals] [-f] [-c] [-b]\n",
      "                             [-k TESTNAMEPATTERNS]\n",
      "                             [tests [tests ...]]\n",
      "ipykernel_launcher.py: error: argument -f/--failfast: ignored explicit argument 'C:\\\\Users\\\\XiaoM\\\\AppData\\\\Local\\\\Temp\\\\tmp-9128nL1G1Qm98mnN.json'\n",
      "ERROR:root:Internal Python error in the inspect module.\n",
      "Below is the traceback from this internal error.\n",
      "\n",
      "Traceback (most recent call last):\n",
      "  File \"D:\\Program Files\\Anaconda3\\lib\\argparse.py\", line 1781, in parse_known_args\n",
      "    namespace, args = self._parse_known_args(args, namespace)\n",
      "  File \"D:\\Program Files\\Anaconda3\\lib\\argparse.py\", line 1987, in _parse_known_args\n",
      "    start_index = consume_optional(start_index)\n",
      "  File \"D:\\Program Files\\Anaconda3\\lib\\argparse.py\", line 1909, in consume_optional\n",
      "    raise ArgumentError(action, msg % explicit_arg)\n",
      "argparse.ArgumentError: argument -f/--failfast: ignored explicit argument 'C:\\\\Users\\\\XiaoM\\\\AppData\\\\Local\\\\Temp\\\\tmp-9128nL1G1Qm98mnN.json'\n",
      "\n",
      "During handling of the above exception, another exception occurred:\n",
      "\n",
      "Traceback (most recent call last):\n",
      "  File \"D:\\Program Files\\Anaconda3\\lib\\site-packages\\IPython\\core\\interactiveshell.py\", line 3417, in run_code\n",
      "    exec(code_obj, self.user_global_ns, self.user_ns)\n",
      "  File \"<ipython-input-15-7dd4b21593dc>\", line 32, in <module>\n",
      "    unittest.main()\n",
      "  File \"D:\\Program Files\\Anaconda3\\lib\\unittest\\main.py\", line 100, in __init__\n",
      "    self.parseArgs(argv)\n",
      "  File \"D:\\Program Files\\Anaconda3\\lib\\unittest\\main.py\", line 133, in parseArgs\n",
      "    self._main_parser.parse_args(argv[1:], self)\n",
      "  File \"D:\\Program Files\\Anaconda3\\lib\\argparse.py\", line 1749, in parse_args\n",
      "    args, argv = self.parse_known_args(args, namespace)\n",
      "  File \"D:\\Program Files\\Anaconda3\\lib\\argparse.py\", line 1788, in parse_known_args\n",
      "    self.error(str(err))\n",
      "  File \"D:\\Program Files\\Anaconda3\\lib\\argparse.py\", line 2501, in error\n",
      "    self.exit(2, _('%(prog)s: error: %(message)s\\n') % args)\n",
      "  File \"D:\\Program Files\\Anaconda3\\lib\\argparse.py\", line 2488, in exit\n",
      "    _sys.exit(status)\n",
      "SystemExit: 2\n",
      "\n",
      "During handling of the above exception, another exception occurred:\n",
      "\n",
      "Traceback (most recent call last):\n",
      "  File \"D:\\Program Files\\Anaconda3\\lib\\site-packages\\IPython\\core\\ultratb.py\", line 1169, in get_records\n",
      "    return _fixed_getinnerframes(etb, number_of_lines_of_context, tb_offset)\n",
      "  File \"D:\\Program Files\\Anaconda3\\lib\\site-packages\\IPython\\core\\ultratb.py\", line 316, in wrapped\n",
      "    return f(*args, **kwargs)\n",
      "  File \"D:\\Program Files\\Anaconda3\\lib\\site-packages\\IPython\\core\\ultratb.py\", line 350, in _fixed_getinnerframes\n",
      "    records = fix_frame_records_filenames(inspect.getinnerframes(etb, context))\n",
      "  File \"D:\\Program Files\\Anaconda3\\lib\\inspect.py\", line 1502, in getinnerframes\n",
      "    frameinfo = (tb.tb_frame,) + getframeinfo(tb, context)\n",
      "AttributeError: 'tuple' object has no attribute 'tb_frame'\n"
     ]
    },
    {
     "output_type": "error",
     "ename": "TypeError",
     "evalue": "object of type 'NoneType' has no len()",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mArgumentError\u001b[0m                             Traceback (most recent call last)",
      "\u001b[1;32mD:\\Program Files\\Anaconda3\\lib\\argparse.py\u001b[0m in \u001b[0;36mparse_known_args\u001b[1;34m(self, args, namespace)\u001b[0m\n\u001b[0;32m   1780\u001b[0m         \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1781\u001b[1;33m             \u001b[0mnamespace\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0margs\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_parse_known_args\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mnamespace\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m   1782\u001b[0m             \u001b[1;32mif\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mnamespace\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0m_UNRECOGNIZED_ARGS_ATTR\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mD:\\Program Files\\Anaconda3\\lib\\argparse.py\u001b[0m in \u001b[0;36m_parse_known_args\u001b[1;34m(self, arg_strings, namespace)\u001b[0m\n\u001b[0;32m   1986\u001b[0m             \u001b[1;31m# consume the next optional and any arguments for it\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1987\u001b[1;33m             \u001b[0mstart_index\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mconsume_optional\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mstart_index\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m   1988\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mD:\\Program Files\\Anaconda3\\lib\\argparse.py\u001b[0m in \u001b[0;36mconsume_optional\u001b[1;34m(start_index)\u001b[0m\n\u001b[0;32m   1908\u001b[0m                         \u001b[0mmsg\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0m_\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'ignored explicit argument %r'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1909\u001b[1;33m                         \u001b[1;32mraise\u001b[0m \u001b[0mArgumentError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0maction\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mmsg\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0mexplicit_arg\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m   1910\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mArgumentError\u001b[0m: argument -f/--failfast: ignored explicit argument 'C:\\\\Users\\\\XiaoM\\\\AppData\\\\Local\\\\Temp\\\\tmp-9128nL1G1Qm98mnN.json'",
      "\nDuring handling of the above exception, another exception occurred:\n",
      "\u001b[1;31mSystemExit\u001b[0m                                Traceback (most recent call last)",
      "    \u001b[1;31m[... skipping hidden 1 frame]\u001b[0m\n",
      "\u001b[1;32m<ipython-input-15-7dd4b21593dc>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m     31\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;34m'__main__'\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 32\u001b[1;33m     \u001b[0munittest\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mmain\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;32mD:\\Program Files\\Anaconda3\\lib\\unittest\\main.py\u001b[0m in \u001b[0;36m__init__\u001b[1;34m(self, module, defaultTest, argv, testRunner, testLoader, exit, verbosity, failfast, catchbreak, buffer, warnings, tb_locals)\u001b[0m\n\u001b[0;32m     99\u001b[0m         \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mprogName\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mos\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpath\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mbasename\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0margv\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 100\u001b[1;33m         \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mparseArgs\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0margv\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m    101\u001b[0m         \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mrunTests\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mD:\\Program Files\\Anaconda3\\lib\\unittest\\main.py\u001b[0m in \u001b[0;36mparseArgs\u001b[1;34m(self, argv)\u001b[0m\n\u001b[0;32m    132\u001b[0m         \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 133\u001b[1;33m             \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_main_parser\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mparse_args\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0margv\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m    134\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mD:\\Program Files\\Anaconda3\\lib\\argparse.py\u001b[0m in \u001b[0;36mparse_args\u001b[1;34m(self, args, namespace)\u001b[0m\n\u001b[0;32m   1748\u001b[0m     \u001b[1;32mdef\u001b[0m \u001b[0mparse_args\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0margs\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mnamespace\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1749\u001b[1;33m         \u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0margv\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mparse_known_args\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mnamespace\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m   1750\u001b[0m         \u001b[1;32mif\u001b[0m \u001b[0margv\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mD:\\Program Files\\Anaconda3\\lib\\argparse.py\u001b[0m in \u001b[0;36mparse_known_args\u001b[1;34m(self, args, namespace)\u001b[0m\n\u001b[0;32m   1787\u001b[0m             \u001b[0merr\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0m_sys\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mexc_info\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1788\u001b[1;33m             \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0merror\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mstr\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0merr\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m   1789\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mD:\\Program Files\\Anaconda3\\lib\\argparse.py\u001b[0m in \u001b[0;36merror\u001b[1;34m(self, message)\u001b[0m\n\u001b[0;32m   2500\u001b[0m         \u001b[0margs\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m{\u001b[0m\u001b[1;34m'prog'\u001b[0m\u001b[1;33m:\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mprog\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'message'\u001b[0m\u001b[1;33m:\u001b[0m \u001b[0mmessage\u001b[0m\u001b[1;33m}\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 2501\u001b[1;33m         \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mexit\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0m_\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'%(prog)s: error: %(message)s\\n'\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0margs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;32mD:\\Program Files\\Anaconda3\\lib\\argparse.py\u001b[0m in \u001b[0;36mexit\u001b[1;34m(self, status, message)\u001b[0m\n\u001b[0;32m   2487\u001b[0m             \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_print_message\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmessage\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0m_sys\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstderr\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 2488\u001b[1;33m         \u001b[0m_sys\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mexit\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mstatus\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m   2489\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mSystemExit\u001b[0m: 2",
      "\nDuring handling of the above exception, another exception occurred:\n",
      "\u001b[1;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "    \u001b[1;31m[... skipping hidden 1 frame]\u001b[0m\n",
      "\u001b[1;32mD:\\Program Files\\Anaconda3\\lib\\site-packages\\IPython\\core\\interactiveshell.py\u001b[0m in \u001b[0;36mshowtraceback\u001b[1;34m(self, exc_tuple, filename, tb_offset, exception_only, running_compiled_code)\u001b[0m\n\u001b[0;32m   2036\u001b[0m                            'the full traceback.\\n']\n\u001b[0;32m   2037\u001b[0m                     stb.extend(self.InteractiveTB.get_exception_only(etype,\n\u001b[1;32m-> 2038\u001b[1;33m                                                                      value))\n\u001b[0m\u001b[0;32m   2039\u001b[0m                 \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   2040\u001b[0m                     \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mD:\\Program Files\\Anaconda3\\lib\\site-packages\\IPython\\core\\ultratb.py\u001b[0m in \u001b[0;36mget_exception_only\u001b[1;34m(self, etype, value)\u001b[0m\n\u001b[0;32m    821\u001b[0m         \u001b[0mvalue\u001b[0m \u001b[1;33m:\u001b[0m \u001b[0mexception\u001b[0m \u001b[0mvalue\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    822\u001b[0m         \"\"\"\n\u001b[1;32m--> 823\u001b[1;33m         \u001b[1;32mreturn\u001b[0m \u001b[0mListTB\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstructured_traceback\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0metype\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m    824\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    825\u001b[0m     \u001b[1;32mdef\u001b[0m \u001b[0mshow_exception_only\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0metype\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mevalue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mD:\\Program Files\\Anaconda3\\lib\\site-packages\\IPython\\core\\ultratb.py\u001b[0m in \u001b[0;36mstructured_traceback\u001b[1;34m(self, etype, evalue, etb, tb_offset, context)\u001b[0m\n\u001b[0;32m    700\u001b[0m                     chained_exceptions_tb_offset, context)\n\u001b[0;32m    701\u001b[0m                 \u001b[1;33m+\u001b[0m \u001b[0mchained_exception_message\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 702\u001b[1;33m                 + out_list)\n\u001b[0m\u001b[0;32m    703\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    704\u001b[0m         \u001b[1;32mreturn\u001b[0m \u001b[0mout_list\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mD:\\Program Files\\Anaconda3\\lib\\site-packages\\IPython\\core\\ultratb.py\u001b[0m in \u001b[0;36mstructured_traceback\u001b[1;34m(self, etype, value, tb, tb_offset, number_of_lines_of_context)\u001b[0m\n\u001b[0;32m   1434\u001b[0m             \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtb\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mtb\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1435\u001b[0m         return FormattedTB.structured_traceback(\n\u001b[1;32m-> 1436\u001b[1;33m             self, etype, value, tb, tb_offset, number_of_lines_of_context)\n\u001b[0m\u001b[0;32m   1437\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1438\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mD:\\Program Files\\Anaconda3\\lib\\site-packages\\IPython\\core\\ultratb.py\u001b[0m in \u001b[0;36mstructured_traceback\u001b[1;34m(self, etype, value, tb, tb_offset, number_of_lines_of_context)\u001b[0m\n\u001b[0;32m   1334\u001b[0m             \u001b[1;31m# Verbose modes need a full traceback\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1335\u001b[0m             return VerboseTB.structured_traceback(\n\u001b[1;32m-> 1336\u001b[1;33m                 \u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0metype\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtb\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtb_offset\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mnumber_of_lines_of_context\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m   1337\u001b[0m             )\n\u001b[0;32m   1338\u001b[0m         \u001b[1;32melif\u001b[0m \u001b[0mmode\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;34m'Minimal'\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mD:\\Program Files\\Anaconda3\\lib\\site-packages\\IPython\\core\\ultratb.py\u001b[0m in \u001b[0;36mstructured_traceback\u001b[1;34m(self, etype, evalue, etb, tb_offset, number_of_lines_of_context)\u001b[0m\n\u001b[0;32m   1191\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1192\u001b[0m         formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,\n\u001b[1;32m-> 1193\u001b[1;33m                                                                tb_offset)\n\u001b[0m\u001b[0;32m   1194\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1195\u001b[0m         \u001b[0mcolors\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mColors\u001b[0m  \u001b[1;31m# just a shorthand + quicker name lookup\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mD:\\Program Files\\Anaconda3\\lib\\site-packages\\IPython\\core\\ultratb.py\u001b[0m in \u001b[0;36mformat_exception_as_a_whole\u001b[1;34m(self, etype, evalue, etb, number_of_lines_of_context, tb_offset)\u001b[0m\n\u001b[0;32m   1148\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1149\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1150\u001b[1;33m         \u001b[0mlast_unique\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mrecursion_repeat\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mfind_recursion\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0morig_etype\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mevalue\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mrecords\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m   1151\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1152\u001b[0m         \u001b[0mframes\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mformat_records\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mrecords\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mlast_unique\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mrecursion_repeat\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mD:\\Program Files\\Anaconda3\\lib\\site-packages\\IPython\\core\\ultratb.py\u001b[0m in \u001b[0;36mfind_recursion\u001b[1;34m(etype, value, records)\u001b[0m\n\u001b[0;32m    449\u001b[0m     \u001b[1;31m# first frame (from in to out) that looks different.\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    450\u001b[0m     \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mis_recursion_error\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0metype\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mrecords\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 451\u001b[1;33m         \u001b[1;32mreturn\u001b[0m \u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mrecords\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m    452\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    453\u001b[0m     \u001b[1;31m# Select filename, lineno, func_name to track frames with\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mTypeError\u001b[0m: object of type 'NoneType' has no len()"
     ]
    }
   ],
   "source": [
    "import unittest\n",
    "\n",
    "class TestStudent(unittest.TestCase):\n",
    "\n",
    "    def test_80_to_100(self):\n",
    "        s1 = Student('Bart', 80)\n",
    "        s2 = Student('Lisa', 100)\n",
    "        self.assertEqual(s1.get_grade(), 'A')\n",
    "        self.assertEqual(s2.get_grade(), 'A')\n",
    "\n",
    "    def test_60_to_80(self):\n",
    "        s1 = Student('Bart', 60)\n",
    "        s2 = Student('Lisa', 79)\n",
    "        self.assertEqual(s1.get_grade(), 'B')\n",
    "        self.assertEqual(s2.get_grade(), 'B')\n",
    "\n",
    "    def test_0_to_60(self):\n",
    "        s1 = Student('Bart', 0)\n",
    "        s2 = Student('Lisa', 59)\n",
    "        self.assertEqual(s1.get_grade(), 'C')\n",
    "        self.assertEqual(s2.get_grade(), 'C')\n",
    "\n",
    "    def test_invalid(self):\n",
    "        s1 = Student('Bart', -1)\n",
    "        s2 = Student('Lisa', 101)\n",
    "        with self.assertRaises(ValueError):\n",
    "            s1.get_grade()\n",
    "        with self.assertRaises(ValueError):\n",
    "            s2.get_grade()\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    unittest.main()"
   ]
  },
  {
   "source": [
    "### 8.4.文档测试"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "对函数 `fact(n)` 编写 doctest 并执行："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def fact(n):\n",
    "    '''\n",
    "    Calculate 1*2*...*n\n",
    "    \n",
    "    >>> fact(1)\n",
    "    1\n",
    "    >>> fact(10)\n",
    "    3628800\n",
    "    >>> fact(-1)\n",
    "    Traceback (most recent call last):\n",
    "        ...\n",
    "    ValueError\n",
    "    '''\n",
    "    if n < 1:\n",
    "        raise ValueError()\n",
    "    if n == 1:\n",
    "        return 1\n",
    "    return n * fact(n - 1)\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    import doctest\n",
    "    doctest.testmod()"
   ]
  }
 ]
}